package com.yyfax.infrastructure.secure;

import com.yyfax.common.CLog;
import org.apaches.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * Created by HenryZhang on 2016/10/31.
 * Copyright © 2016 www.yyfax.com. All Rights Reserved.
 *
 * Copy from newyjkd, write by Yu Feng
 *
 * 可参考：
 * http://blog.csdn.net/bbld_/article/details/38777491
 * http://www.apkbus.com/thread-140480-1-1.html
 *
 * keyFactory.generatePublic(x509KeySpec)抛出的“asn1 encoding routines:ASN1_get_object:header too long”的异常待查
 * http://stackoverflow.com/questions/34299570/string-to-publickey-header-is-too-long
 */
public class RSA {
	private final static String TAG = "RSA";

	public static final String KEY_ALGORITHM = "RSA";
	/**android默认的填充方法和纯java平台不一样*/
//	public static final String KEY_PADDING = "RSA";
	public static final String KEY_PADDING = "RSA/None/PKCS1Padding";

	private static final int MAX_ENCRYPT_BLOCK = 117;
	private static final int MAX_DECRYPT_BLOCK = 128;

	/**
	 * 公钥加密
	 * @param data 待加密的数据
	 * @param publicKey 服务端RSA接口返回
	 * @return
	 */
	public static String encryptByPublicKey(String data, String publicKey) {
		CLog.i(TAG, "data=" + data + ", publicKey=" + publicKey);
		try {
			byte[] dataByte = data.getBytes();
			byte[] keyByte = Base64.decodeBase64(publicKey);
			byte[] binaryData = encryptByPublicKey(dataByte, keyByte);
			return Base64.encodeBase64String(binaryData);
		} catch (Exception e) {
			e.printStackTrace();
//			catch (NoSuchAlgorithmException e) {
//				throw new Exception("无此算法");
//			} catch (InvalidKeySpecException e) {
//				throw new Exception("公钥非法");
//			} catch (NullPointerException e) {
//				throw new Exception("公钥数据为空");
//			}
		}
		return "";
	}

	/**
	 * 公钥加密，用于与手机交互
	 *
	 * @param data 待加密数据
	 * @param keyBytes 密钥字节码
	 * @return 加密后数据
	 * @throws Exception
	 */
	public static byte[] encryptByPublicKey(byte[] data, byte[] keyBytes) throws Exception {
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
		PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
		try {
			RSAPublicKey key = (RSAPublicKey) publicK;//also work to private key
			CLog.i(TAG, "encryptByPublicKey: Algorithm=" + publicK.getAlgorithm());
			CLog.i(TAG, "encryptByPublicKey: Format=" + publicK.getFormat());
			CLog.i(TAG, "encryptByPublicKey: Modulus=" + key.getModulus().toString());
			CLog.i(TAG, "encryptByPublicKey: Modulus.length=" + key.getModulus().bitLength());
			CLog.i(TAG, "encryptByPublicKey: PublicExponent=" + key.getPublicExponent().toString());
			CLog.i(TAG, "encryptByPublicKey: PublicExponent.length=" + key.getPublicExponent().bitLength());
		} catch (Exception e) {
			CLog.i(TAG, "encryptByPublicKey: can't change PublicKey to RSAPublicKey");
		}

		// 对数据加密
		Cipher cipher = Cipher.getInstance(KEY_PADDING);
		cipher.init(Cipher.ENCRYPT_MODE, publicK);
		int inputLen = data.length;
		ByteArrayOutputStream out = null;
		byte[] encryptedData = null;
		try {
			out = new ByteArrayOutputStream();
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段加密
			while (inputLen - offSet > 0) {
				if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
					cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(data, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_ENCRYPT_BLOCK;
			}
			out.flush();
			encryptedData = out.toByteArray();
		} finally {
			out.close();
			out = null;
		}
		return encryptedData;
	}

	/**
	 * 私钥解密，用于与手机交互
	 *
	 * @param data 加密数据
	 * @param keyBytes 密钥字节码
	 * @return 解密后数据
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKey(byte[] data, byte[] keyBytes) throws Exception {
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);//用的是PKCS#8编码的Key指令
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(KEY_PADDING);
		cipher.init(Cipher.DECRYPT_MODE, privateK);
		int inputLen = data.length;
		ByteArrayOutputStream out = null;
		byte[] decryptedData = null;
		try {
			out = new ByteArrayOutputStream();
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段解密
			while (inputLen > offSet) {
				if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
					cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(data, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_DECRYPT_BLOCK;
			}
			out.flush();
			decryptedData = out.toByteArray();
		} finally {
			out.close();
			out = null;
		}
		return decryptedData;
	}

	/**
	 * 私钥加密，用于与核心交互
	 *
	 * @param data 原数据
	 * @param keyBytes 密钥字节码
	 * @return 加密后的数据
	 * @throws Exception
	 */
	public static byte[] encryptByPrivateKey(byte[] data, byte[] keyBytes) throws Exception {
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, privateK);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		byte[] cache;
		int i = 0;
		// 对数据分段加密
		int length = data.length;
		int offSet = 0;
		while (length - offSet > 0) {
			if (length - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, length - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

	/**
	 * 公钥解密
	 * @param data 待解密的数据
	 * @param publicKey 服务端RSA接口返回
	 * @return
	 */
	public static String decryptByPublicKey(String data, String publicKey) {
		CLog.i(TAG, "data=" + data + ", publicKey=" + publicKey);
		try {
			byte[] dataByte = Base64.decodeBase64(data);
			byte[] keyByte = Base64.decodeBase64(publicKey);
			byte[] binaryData = decryptByPublicKey(dataByte, keyByte);
			return Base64.encodeBase64String(binaryData);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * 公钥解密，用于与核心交互
	 *
	 * @param data 加密数据
	 * @param keyBytes 密钥字节码
	 * @return 解密后数据
	 * @throws Exception
	 */
	public static byte[] decryptByPublicKey(byte[] data, byte[] keyBytes) throws Exception {
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
		Cipher cipher = Cipher.getInstance(KEY_PADDING);
		cipher.init(Cipher.DECRYPT_MODE, publicK);
		byte[] decryptedData;
		ByteArrayOutputStream out = null;
		try {
			out = new ByteArrayOutputStream();
			byte[] cache;
			int i = 0;
			int length = data.length;
			int offSet = 0;
			// 对数据分段解密
			while (length - offSet > 0) {
				if (length - offSet > MAX_DECRYPT_BLOCK) {
					cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(data, offSet, length - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_DECRYPT_BLOCK;
			}
			decryptedData = out.toByteArray();
		} finally {
			if (null != out) {
				out.close();
			}
		}
		return decryptedData;
	}
}

